package com.have.blog.admin.annotation.operationLogger;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.have.blog.admin.domain.bo.AdminUserDetail;
import com.have.blog.config.hloder.RequestHolder;
import com.have.blog.model.mapper.ExceptionLogMapper;
import com.have.blog.model.mapper.SysLogMapper;
import com.have.blog.model.models.ExceptionLog;
import com.have.blog.model.models.SysLog;
import com.have.blog.utils.AspectUtil;
import com.have.blog.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.*;

/**
 * Created By Have
 * 2021/7/19 10:42
 */
@Aspect
@Slf4j
@Component
public class LoggerAspect {

    @Autowired
    private SysLogMapper logMapper;
    @Autowired
    private ExceptionLogMapper exceptionLogMapper;

    private ThreadLocal<Date> startTime = new ThreadLocal<>();

    private static final ExecutorService logThreadPool =  Executors.newSingleThreadExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(@NotNull Runnable r) {
            return new Thread(r, "log-Thread");
        }
    });


    @Pointcut(value = "@annotation(operationLogger)")
    public void pointcut(OperationLogger operationLogger) {}

    @AfterThrowing(value = "pointcut(operationLogger)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLogger operationLogger, Throwable e) {
        ExceptionLog exceptionLog = new ExceptionLog();
        HttpServletRequest request = RequestHolder.getRequest();
        String ip = IpUtils.getIpAddr(request);
        String operationName = AspectUtil.parseParams(joinPoint.getArgs(), operationLogger.value());
        String ipSource = IpUtils.getAddresses("ip=" + ip, "utf-8");

        exceptionLog.setIp(ip);
        exceptionLog.setMethod(joinPoint.getSignature().getName());
        exceptionLog.setExceptionJson(JSONObject.toJSONString(e,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.IgnoreNonFieldGetter));
        exceptionLog.setExceptionMessage(e.getMessage());
        exceptionLog.setOperation(operationName);
        exceptionLog.setIpSource(ipSource);
        logThreadPool.submit(new ExceptionTask(exceptionLog));
    }

    @Around(value = "pointcut(operationLogger)")
    public Object doAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {
        startTime.set(new Date());
        Object result = joinPoint.proceed();

        handle(joinPoint);

        startTime.remove();
        return result;
    }

    private void handle(ProceedingJoinPoint joinPoint) throws Exception {
        HttpServletRequest request = RequestHolder.getRequest();
        Method method = AspectUtil.getMethod(joinPoint);
        OperationLogger annotation = method.getAnnotation(OperationLogger.class);
        boolean save = annotation.save();
        String bussisnessName = AspectUtil.parseParams(joinPoint.getArgs(), annotation.value());
        String userAgent = request.getHeader("User-Agent");
        String ip = IpUtils.getIpAddr(request);
        String addresses = IpUtils.getAddresses("ip=" + ip, "utf-8");
        String requestMethod = request.getMethod();
        String uri = request.getRequestURI();
        AdminUserDetail principal = (AdminUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String username = principal.getUsername();
        Integer adminUid = principal.getAdmin().getUid();
        log.info("{} | {} - {} {} - {}", bussisnessName, ip, request.getMethod(), request.getRequestURL(), userAgent);
        if (!save) {
            return;
        }
        Map<String, Object> nameAndArgsMap = AspectUtil.getFieldsName(joinPoint);
        String paramJsonString = null;
        paramJsonString = JSONObject.toJSONString(nameAndArgsMap, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.IgnoreNonFieldGetter);


        logThreadPool.submit(
                new LogTask(
                        ip, addresses, requestMethod, uri,
                        username, adminUid,
                        paramJsonString,
                        joinPoint.getTarget().getClass().getName(),
                        joinPoint.getSignature().getName(),
                        startTime.get(),
                        bussisnessName
                ));
    }

    private class ExceptionTask implements Runnable {
        private ExceptionLog exceptionLog;

        public ExceptionTask(ExceptionLog exceptionLog) {
            this.exceptionLog = exceptionLog;
        }

        @Override
        public void run() {
            exceptionLogMapper.insert(exceptionLog);
        }
    }

    private class LogTask implements Runnable {

        private String  ip;
        private String addresses;
        private String requestMethod;
        private String uri;

        private String username;
        private Integer adminUid;
        /**
         * 参数列表
         */
        private String paramsJson;

        /**
         * 类路径
         */
        private String classPath;

        /**
         * 方法名
         */
        private String methodName;

        /**
         * 方法请求时间
         */
        private Date startTime;

        /**
         * 操作名称
         */
        private String operationName;

        public LogTask(String ip, String addresses, String requestMethod, String uri,
                       String username, Integer adminUid,
                       String paramsJson,
                       String classPath, String methodName,
                       Date startTime, String operationName) {
            this.ip = ip;
            this.addresses = addresses;
            this.requestMethod = requestMethod;
            this.uri = uri;
            this.username = username;
            this.adminUid = adminUid;
            this.paramsJson = paramsJson;
            this.classPath = classPath;
            this.methodName = methodName;
            this.startTime = startTime;
            this.operationName = operationName;
        }

        @Override
        public void run() {
            SysLog sysLog = new SysLog();
            sysLog.setIp(ip);
            sysLog.setIpSource(addresses);
            sysLog.setClassPath(classPath);
            sysLog.setMethod(methodName);
            sysLog.setType(requestMethod);
            sysLog.setUrl(uri);
            sysLog.setOperation(operationName);
            sysLog.setAdminUid(adminUid);
            sysLog.setUsername(username);
            sysLog.setParams(paramsJson);
            Date endTime = new Date();
            long spendTime = DateUtil.between(startTime, endTime, DateUnit.MS);
            sysLog.setSpendTime(spendTime);
            logMapper.insert(sysLog);
        }
    }
}
